DNA Sequence Viewer App



One of my favorite things to do is craft graphical user interface (GUI) applications. It can be really fulfilling when you write an app that satifies some purpose or need. There's a lot you can do and the possibilities seem almost endless.

A friend of mine who majored in micro-biology, in undergrad, had to write a .fasta file reader. Fasta format is just a standardized way in which DNA sequences are stored in a file. She only had to write a command line interface program to read and display the file. I asked her if she would send me the project. She obliged my request and I did make the command line fasta program. I realized it was a really good opportunity to write it into a GUI application. It was one of the first GUI apps I ever wrote—I learned a lot while writing it.

There are three prominent modules in Python for writing GUI apps; Kivy, PyQt and Tkinter. I started out with Kivy but have come to realize that PyQt is far superior—at least for writing desktop applications. I havn't used Tkinter much at all.

The first version of the DNA Sequence Viewer that I wrote used Kivy. I then re-wrote the code using PyQt5. For me this was good exersize becuase both modules do have different strengths and weaknesses. Kivy has a really unique look—it's somewhat intended for mobile applications. Whilst PyQt is a very solid desktop application module.



PyQt Version

PyQt5 Version Demo Video:


PyQt5 Imports


#IMPORTS
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
import sys
from collections import Counter
import pandas as pd
import matplotlib.pyplot as plt

Brief review of PyQt5 methods

STEPS 1 and 2:
One of the first tasks after importing the necessary modules and instantiating a class for your window is to instantiate a layout—somewhat like a master layout that all other layouts will reside in.


#IMPORTS
from PyQt5.QtWidgets import * 
from PyQt5.QtGui import *
from PyQt5.QtCore import *
import sys
from collections import Counter
import pandas as pd
import matplotlib.pyplot as plt

class WindowClassOne(QWidget):
    def __init__(self):
        super(WindowClassOne,self).__init__()
        #SET THE DEFAULT FONT FOR APP
        self.setFont(QFont('consolas',11))
        #SET DEFUALT MINIMUM SIZE OF WINDOW
        self.setMinimumSize(900,900)
        
        #INSTANTIATE AN OUTER LAYOUT (master layout); STEP 1
        self.outerLayout = QHBoxLayout()
        
        #ADD THE MAIN WINDOWS OUTER LAYOUT (MASTER LAYOUT); STEP 2
        self.setLayout(self.outerLayout)

if __name__ == "__main__":
    app = QApplication(sys.argv)
    app.setStyle('Fusion') 
    window = WindowClassOne()
    window.show()
    sys.exit(app.exec_())

When you run the above it outputs a blank window. This is where you add layouts and widgets.

qt_basic_win

Steps 3 and 4:
Add a sub-layouts and/or widgets to the main layout (here named self.outerLayout). This next step depends on what your app requires. For my purposes here I need to add a left and right sublayout so I'll instantiat them and call them to the main layout.


#IMPORTS
from PyQt5.QtWidgets import * 
from PyQt5.QtGui import *
from PyQt5.QtCore import *
import sys
from collections import Counter
import pandas as pd
import matplotlib.pyplot as plt

class WindowClassOne(QWidget):
    def __init__(self):
        super(WindowClassOne,self).__init__()
        #SET THE DEFAULT FONT FOR APP
        self.setFont(QFont('consolas',11))
        #SET DEFUALT MINIMUM SIZE OF WINDOW
        self.setMinimumSize(900,900)
        
        #INSTANTIATE AN OUTER LAYOUT (master layout); STEP 1
        self.outerLayout = QHBoxLayout()
        
        #ADD THE MAIN WINDOWS OUTER LAYOUT (MASTER LAYOUT); STEP 2
        self.setLayout(self.outerLayout)
        
        #INSTANTIATE LEFT AND RIGHT LAYOUTS (sub layouts); STEP 3
        self.leftLayout = QGridLayout()
        self.rightLayout = QGridLayout()
        self.subformlayout_1 = QFormLayout()
        self.subrightlayout = QGridLayout()
        
        #CALL THE INNER LAYOUTS INTO THE OUTER LAYOUT (MASTER LAYOUT); STEP 4
        self.outerLayout.addLayout(self.leftLayout, 7) #the integer indicates respective stretch 
        self.outerLayout.addLayout(self.rightLayout, 2) #the integer indicates respective stretch 

if __name__ == "__main__":
    app = QApplication(sys.argv)
    app.setStyle('Fusion') 
    window = WindowClassOne()
    window.show()
    sys.exit(app.exec_())

The above will still yield a blank window but the layouts are created and called.

Steps 5 and 6: Instantiate and call widgets to the layouts.


#IMPORTS
from PyQt5.QtWidgets import * 
from PyQt5.QtGui import *
from PyQt5.QtCore import *
import sys
from collections import Counter
import pandas as pd
import matplotlib.pyplot as plt

class WindowClassOne(QWidget):
    def __init__(self):
        super(WindowClassOne,self).__init__()
        #SET THE DEFAULT FONT FOR APP
        self.setFont(QFont('consolas',11))
        #SET DEFUALT MINIMUM SIZE OF WINDOW
        self.setMinimumSize(900,900)
        
        #INSTANTIATE AN OUTER LAYOUT (master layout); STEP 1
        self.outerLayout = QHBoxLayout()
        
        #ADD THE MAIN WINDOWS OUTER LAYOUT (MASTER LAYOUT); STEP 2
        self.setLayout(self.outerLayout)
        
        #INSTANTIATE LEFT AND RIGHT LAYOUTS (sub layouts); STEP 3
        self.leftLayout = QGridLayout()
        self.rightLayout = QGridLayout()
        self.subformlayout_1 = QFormLayout()
        self.subrightlayout = QGridLayout()
        
        #CALL THE INNER LAYOUTS INTO THE OUTER LAYOUT (MASTER LAYOUT); STEP 4
        self.outerLayout.addLayout(self.leftLayout, 7) #the integer indicates respective stretch 
        self.outerLayout.addLayout(self.rightLayout, 2) #the integer indicates respective stretch 
        
        #INSTANTIATE WIDGETS (added in order) STEP 5
        self.left_layout_textedit = QTextEdit('LEFT LAYOUT HERE...', readOnly=True)
        self.right_layout_textedit = QTextEdit('RIGHT LAYOUT HERE...', readOnly=True)
        self.some_button_example = QPushButton('SOME BUTTON')
        
        #CALL THE WIDGETS TO THE LEFT AND RIGHT LAYOUTS; STEP 6
        #you have to declare where the widget goes in the gridlayout here
        #so syntax; SomeLayout.addWidget(SomeWidget, gridCoord_y, gridCoord_x)
        #span multi syntax; SomeLayout.addWidget(SomeWidget, gridCoord_y, gridCoord_x, gridCoord_y2, gridCoord_x2)
        self.leftLayout.addWidget(self.left_layout_textedit, 0, 0, 1, 0)
        self.rightLayout.addWidget(self.right_layout_textedit, 1, 0, 1, 0) 
        self.rightLayout.addWidget(self.some_button_example, 0, 0, 1, 0) 
        
if __name__ == "__main__":
    app = QApplication(sys.argv)
    app.setStyle('Fusion') 
    window = WindowClassOne()
    window.show()
    sys.exit(app.exec_())

When you run the code after adding steps 5 and 6 we can see the widgets in the app. But the button wont do anything until we; write a function and bind it to the button.

qt_basic_win_2

Steps 7 and 8: Write a function and bind (connect) it to the button.


#IMPORTS
from PyQt5.QtWidgets import * 
from PyQt5.QtGui import *
from PyQt5.QtCore import *
import sys
from collections import Counter
import pandas as pd
import matplotlib.pyplot as plt

class WindowClassOne(QWidget):
    def __init__(self):
        super(WindowClassOne,self).__init__()
        #set the default font for app
        self.setFont(QFont('consolas',11))
        #SET DEFUALT MINIMUM SIZE OF WINDOW
        self.setMinimumSize(900,900)
        
        #INSTANTIATE AN OUTER LAYOUT (master layout); STEP 1
        self.outerLayout = QHBoxLayout()
        
        #ADD THE MAIN WINDOWS OUTER LAYOUT (MASTER LAYOUT); STEP 2
        self.setLayout(self.outerLayout)
        
        #INSTANTIATE LEFT AND RIGHT LAYOUTS (sub layouts); STEP 3
        self.leftLayout = QGridLayout()
        self.rightLayout = QGridLayout()
        self.subformlayout_1 = QFormLayout()
        self.subrightlayout = QGridLayout()
        
        #CALL THE INNER LAYOUTS INTO THE OUTER LAYOUT (MASTER LAYOUT); STEP 4
        self.outerLayout.addLayout(self.leftLayout, 7) #the integer indicates respective stretch 
        self.outerLayout.addLayout(self.rightLayout, 2) #the integer indicates respective stretch 
        
        #INSTANTIATE WIDGETS (added in order) STEP 5
        self.left_layout_textedit = QTextEdit('LEFT LAYOUT HERE...', readOnly=True)
        self.right_layout_textedit = QTextEdit('RIGHT LAYOUT HERE...', readOnly=True)
        self.some_button_example = QPushButton('SOME BUTTON')
        
        #CALL THE WIDGETS TO THE LEFT AND RIGHT LAYOUTS; STEP 6
        #you have to declare where the widget goes in the gridlayout here
        #so syntax; SomeLayout.addWidget(SomeWidget, gridCoord_y, gridCoord_x)
        #span multi syntax; SomeLayout.addWidget(SomeWidget, gridCoord_y, gridCoord_x, gridCoord_y2, gridCoord_x2)
        self.leftLayout.addWidget(self.left_layout_textedit, 0, 0, 1, 0)
        self.rightLayout.addWidget(self.right_layout_textedit, 1, 0, 1, 0) 
        self.rightLayout.addWidget(self.some_button_example, 0, 0, 1, 0) 
        
        #WRITE A FUNCTION THAT DOES SOMETHING; STEP 7
        def something_to_do(self):
            self.left_layout_textedit.append('This text is appended to the LEFT when the button is pushed')
            self.right_layout_textedit.append('This text is appended to the RIGHT when the button is pushed')
        
        #BIND (CONNECT) THE BUTTON TO THE FUNCTION; STEP 8
        self.some_button_example.clicked.connect(lambda checked: something_to_do(self))
        
if __name__ == "__main__":
    app = QApplication(sys.argv)
    app.setStyle('Fusion') 
    window = WindowClassOne()
    window.show()
    sys.exit(app.exec_())

Now when we run the program and click the button, whatever is in the function (something_to_do) is executed.

qt_basic_win_3

Depending on what program you are trying to write and what you want it to do this process can get rather complex. For now I'm going to leave the brief overview at this.

Full PyQt5 ANG DNA Parser V5_1: The program I wrote to parse and view fasta files pretty much uses everything above—although with more layouts, widgets, and functions.

This program isn't perfect and there is a lot I want to do to it. It works as intended but I do need to fix some of the try/except statements in some of the functions. I also want to add an data log, exporter, and more graphing functions. For now I need to move on to other things.

I've done my best to comment heavily, here is the full code:


# -*- coding: utf-8 -*-
"""
@author: ANG
"""
#IMPORTS
from PyQt5.QtWidgets import * 
from PyQt5.QtGui import *
from PyQt5.QtCore import *
import sys
from collections import Counter
import pandas as pd
import matplotlib.pyplot as plt

#TO CATCH PYtoEXE ERRORS
def except_hooky(type, value, traceback, oldhook=sys.excepthook):
    '''
    Had a slight issue converting this file to an exe. This function freezes the 
    output so I could read the TraceBack error. Without this it closed too fast 
    for me to read it.
    '''
    oldhook(type, value, traceback)
    input("Press RETURN. ")    
sys.excepthook = except_hooky

class WindowClassOne(QWidget):
    def __init__(self):
        super(WindowClassOne,self).__init__()
        #set the default font for app
        self.setFont(QFont('consolas',11))
        #INSTANTIATE AN OUTER LAYOUT (master layout) #STEP 1
        self.outerLayout = QHBoxLayout()
        #INSTANTIATE LEFT AND RIGHT LAYOUTS (sub layouts) #STEP 3
        self.leftLayout = QGridLayout()
        self.rightLayout = QGridLayout()
        self.subformlayout_1 = QFormLayout()
        self.subrightlayout = QGridLayout()
        #INSTANTIATE LEFTLAYOUT WIDGETS #STEP 5
        self.seq_main_text_input = QTextEdit('', readOnly=True)
        self.seq_main_text_input.moveCursor(1, 0)
        #INSTANTIATE LAYOUT WIDGETS #STEP 6
        #ACTION MESSAGE
        self.action_message_text_input = QTextEdit('Load a fasta file...', readOnly=True)
        self.action_message_text_input.setMaximumHeight(60)
        #SIDE BAR STATS TEXT
        self.side_bar_text_input = QTextEdit('Load a fasta file... ', readOnly=True)
        #LOAD FASTA BUTTON
        self.load_fasta_button = QPushButton('Load Fasta File')
        #SELECT SEQ BUTTON
        self.select_seq_button = QPushButton('Select Sequence')
        #SELECT SEQ INPUT START
        self.select_seq_text_box = QLineEdit('')
        self.select_seq_text_box.setMaximumWidth(100)
        #SELECT SEQ INPUT STOP
        self.select_seq_text_box_stop = QLineEdit('Stop')
        #SPACER TOGGLE LABEL
        self.spacer_tog_label = QLabel('Spacer Toggle')
        #SPACER TOGGLE CHECKBOX
        self.spacer_tog_checkbox = QCheckBox()
        #SHOW SEQ BY 50 BUTTON
        self.show_seq_data_by_50 = QPushButton('Show Seq Data By 50')
        #SHOW SEQ BY 100 BUTTON
        self.show_seq_data_by_100 = QPushButton('Show Seq Data By 100')
        #PLOT GC DATA BUTTON
        self.plot_gc_button = QPushButton('Plot GC Data')
        #ADD WIDGETS TO LEFT LAYOUT (added in order) STEP 7
        self.leftLayout.addWidget(self.seq_main_text_input,0,3,1,1)
        #CALL WIDGETS TO RIGHT LAYOUT (added in order) STEP 8
        #you have to declare where the widget goes in the gridlayout here
        #so syntax; SomeLayout.addWidget(SomeWidget, gridCoord_y, gridCoord_x)
        #span multi syntax; SomeLayout.addWidget(SomeWidget, gridCoord_y, gridCoord_x, gridCoord_y2, gridCoord_x2)
        self.rightLayout.addWidget(self.action_message_text_input, 0, 0, 1, 0) 
        self.rightLayout.addWidget(self.side_bar_text_input, 1, 0, 1, 1)  
        self.rightLayout.addWidget(self.load_fasta_button, 2, 0, 1, 1)
        #CALL THE INNER LAYOUTS INTO THE OUTER LAYOUT (MASTER LAYOUT) #STEP 4
        self.outerLayout.addLayout(self.leftLayout, 7) #the integer indicates respective stretch 
        self.outerLayout.addLayout(self.rightLayout, 2) #the integer indicates respective stretch 
        #CALL THE INNER LAYOUTS (SUB-LAYOUTS)
        self.rightLayout.addLayout(self.subformlayout_1, 3, 0)
        self.rightLayout.addLayout(self.subrightlayout, 4, 0,)
        #INSTANTIATE THE SUB FORM LAYOUTS
        self.subformlayout_1.addRow(self.select_seq_button, self.select_seq_text_box, )
        self.subformlayout_1.addRow(self.spacer_tog_label, self.spacer_tog_checkbox)
        #ADD TO SUBRIGHT LAYOUT
        self.subrightlayout.addWidget(self.show_seq_data_by_50, 1, 0, 1, 1)
        self.subrightlayout.addWidget(self.show_seq_data_by_100, 1, 1, 1, 1)
        self.subrightlayout.addWidget(self.plot_gc_button, 2, 0, 1, 1)
        #SET SOME STYLES
        #SET STYLE OF WHOLE APP
        self.setStyleSheet("background-color : #ADC6C5 ; height:30px ; font: consolas ")
        #INSTANTIATE FUNCTION TO DO SOMETHING (CHANGE THE ACTION TEXT BOX TEXT) #STEP 9
        def changer(self): 
            self.action_message_text_input.setText('text has been edited')
            self.seq_main_text_input.insertPlainText('\nYou need to load a .fasta...')
            print('button pressed')
            
        def load_fasta_alert(self): 
            self.load_fasta_alert = QMessageBox()
            self.load_fasta_alert.setWindowTitle('Alert!!!')
            self.load_fasta_alert.setText('You need to load a .fasta file...')
            self.load_fasta_alert.exec_()

        #FILE LOADER FUNCTION (very simple file loader)
        def load_file(self): 
            ''' 
            Intended to load the file as an object. Essentially this function 
            takes the text in the .fasta file as a string. Separates it every greater 
            than operator. Then splits the titles that start with greater than operator 
            and the actual sequence data into to separate lists with the same 
            index. In retrospect it may have been more efficient to use a dictionary 
            but the code functioned as intended so I left it as lists. [Works as intended].
            ''' 
            try:
                #OPEN_FILE RETURNS A TUPLE
                filename = QFileDialog.getOpenFileName(None, 'Open file', './', 'FASTA Files (*.fasta)')
                txt = open(filename[0], 'r')
                with txt:
                    self.data = txt.read()
                    self.filename = filename[0].split('/') 
                    print(self.filename)
                #####Segment the data into respective [data_keys] [data_vals] lists#####
                genes = self.data.split('>')[1:] 
                seqdic = {} 
                for gene in genes:
                    gene = gene.strip().split('\n') 
                    seqdic['>'+gene[0]] = ''.join(gene[1:]) 
                seqdic_keys_lst = list(seqdic.keys()) 
                seqdic_vals_lst = list(seqdic.values()) 
                self.original_data_lists = seqdic_vals_lst 
                #REMOVE A STRING VALUE FROM A LIST OF STRINGS
                self.data_keys = seqdic_keys_lst 
                self.data_vals = seqdic_vals_lst 
                #print(self.data_keys, self.data_vals)#Printed for debugging
                self.spacer = 'Off'
                #SIDE BAR DISPLAY
                self.side_bar_text_input.setText('')
                self.side_bar_text_1 = 'There are {0} sequences in {1}\n'.format(len(self.data_keys), self.filename[-1]) 
                self.side_bar_text_2 = 'Sequence selection range: 0 - {}\n'.format(len(self.data_keys)-1)
                self.side_bar_text_3 = '\nSequence Selected: '   
                side_bar_str_lst = [self.side_bar_text_1, self.side_bar_text_2, self.side_bar_text_3]
                for i in side_bar_str_lst: 
                    self.side_bar_text_input.insertPlainText(i)
                self.action_message_text_input.setText('')
                self.action_message_text_input.insertPlainText('Fasta file has loaded...\nChoose a sequence...')
            except:
                self.action_message_text_input.setPlainText('File Load canceled...')
                
        def reselect_seq_alert(self): 
            '''
            This function calls an alert box that tells the user their input into 
            the select sequence text input was invalid. 
            '''
            self.load_fasta_alert = QMessageBox()
            self.load_fasta_alert.setWindowTitle('Alert!!')
            self.load_fasta_alert.setText('The sequence you selected is out of range.\nOr you need to input an integer.\nre-select a sequence')
            self.load_fasta_alert.exec_()
            
        def load_a_fasta_alert(self):
            '''
            This function calls an alert box that tells the user they need to 
            load a .fasta file if one is not already loaded. 
            '''
            self.load_fasta_alert = QMessageBox()
            self.load_fasta_alert.setWindowTitle('Alert!!')
            self.load_fasta_alert.setText('User needs to load a .fasta')
            self.load_fasta_alert.exec_()
        
        def select_seq(self):
            '''
            This function assigns the text the user input as the selected seq. 
            It then checks that the number input is in the range of the index of 
            data keys. If it is not it alerts the user in the upper right action 
            message box. 
            '''
            try:
                self.seq_selected = self.select_seq_text_box.text()
                self.seq_keys_nums = [str(i) for i in range(0, len(self.data_keys))] 
                if self.seq_selected not in self.seq_keys_nums:
                    self.action_message_text_input.setText('The sequence you selected is out of range.\nOr you need to input an integer.')
                else:
                    pass
            except:
                pass
                
        def select_seq_ana(self):
            ''' 
            Intended to parse the statistics for the selected sequence and display 
            them in the right_layout text box. 
            '''
            try:
                file_data_2 = self.data.split('>')[1:] #converts file_data to list of lists
                seq_selected_idx = int(self.seq_selected) 
                scoped_data = file_data_2[seq_selected_idx].split('\n')[0:] #creates a list of all data split by \n
                self.heading_title = scoped_data[0]
                
                counter_1 = Counter()
                for i in self.original_data_lists[int(self.seq_selected)]:
                    counter_1[i] += 1
                counter_1 = dict(counter_1)
                
                self.counter_at = self.original_data_lists[int(self.seq_selected)].count('AT')
                self.counter_gc = self.original_data_lists[int(self.seq_selected)].count('GC')
                self.counter_cn = self.original_data_lists[int(self.seq_selected)].count('N')
                
                self.n_counts_header = '\nNucleotide counts: \n'
                n_counts_lst = []
                for k, v in counter_1.items(): 
                    n_counts = '{} = {}'.format(k, str(v))
                    n_counts_lst.append(n_counts)
                self.n_counts_lst_str = '\n'.join(n_counts_lst)
                self.total = self.counter_at + self.counter_gc
                
                stat_at = self.counter_at/self.total
                stat_gc = self.counter_gc/self.total
                
                self.at_stat = 'AT = {}  :  AT content = {}'.format(str(self.counter_at), str(round(stat_at, 5)))
                self.gc_stat = 'GC = {}  :  GC content = {}'.format(str(self.counter_gc), str(round(stat_gc, 5)))
                self.n_stat = 'N = {} '.format(str(self.counter_cn))
                self.stats_full = [[i + '\n' for i in n_counts_lst], '\n', self.at_stat, self.gc_stat]
                #reset the select seq text box and side bar stats text box 
                self.side_bar_text_input.setText('')
                self.select_seq_text_box.setText('')
                
                stats_lst = [self.side_bar_text_1, self.side_bar_text_2, 
                                self.side_bar_text_3, self.seq_selected, '\n', self.n_counts_header, 
                                self.n_counts_lst_str, '\n', self.at_stat, 
                                '\n', self.gc_stat, '\n\n', 
                                '**Note: N values are dropped in sequence display']
                for i in stats_lst:
                    self.side_bar_text_input.insertPlainText(i)
                self.action_message_text_input.setText('Sequence selected.') 
            except: 
                try: 
                    if self.data == False:
                        dlg = load_a_fasta_alert(self)
                        self.select_seq_text_box.setText('')
                    else: 
                        dlg = reselect_seq_alert(self)
                        self.select_seq_text_box.setText('')
                except:
                    pass
                
        def show_seq_data_by_50(self): 
            ''' 
            Function intended to print the raw string of seq selected to kivy output. It displays 
            the DNA sequence by 50. An exception is raised if a .fasta file is not 
            yet loaded or a sequence has not been selected. 
            [Works as intended] 
            ''' 
            #at this point self.file_data is the input file as a raw string
            try:     
                self.seq_main_text_input.moveCursor(1, 0)
                if (self.seq_selected) and (self.seq_selected in self.seq_keys_nums):
                    file_data_2 = self.data.split('>')[1:] #converts file_data to list of lists
                    #REMOVE Ns HERE 
                    table = str.maketrans('', '', 'N') 
                    file_data_2 = [s.translate(table) for s in file_data_2]
                    seq_selected_idx = int(self.seq_selected) 
                    #format the text...
                    #file_data_2[seq_selected_idx] is string data of the sequence selected 
                    scoped_data = file_data_2[seq_selected_idx].split('\n')[0:] #creates a list of all data split by \n
                    self.heading_title = scoped_data[0]
                    nucleotide_data = ''.join(map(str,scoped_data[1:]))
                    heading_1_nums = [i for i in range(1,11)]
                    heading_2_nums = '1234567890'
                    self.nucleotide_data = '\n'.join(nucleotide_data[i:i + 50] for i in range(0,len(nucleotide_data), 50))
                    self.nucleotide_data = self.nucleotide_data.split('\n')
                    idx = 0
                    line = 1
                    spaces = '   '
                    #loop to print each string in the list of strings nucleotides 
                    if self.spacer_tog_checkbox.checkState() == 0:
                        
                        heading_1_of_5_ns = '         {0[0]}         {0[1]}         {0[2]}         {0[3]}         {0[4]}'.format(heading_1_nums)
                        heading_2_of_5_ns = 'Line {0}{0}{0}{0}{0}'.format(heading_2_nums)
                        
                        header = ['\n\n' , self.heading_title , '  [Seq Num {}]'.format(self.seq_selected),
                                    '\n', '     ', heading_1_of_5_ns, '\n', heading_2_of_5_ns ]
                        
                        for i in header: 
                            self.seq_main_text_input.insertPlainText(i)
                            
                        line_idx = 1
                        spaces = '   '
                        
                        for i in self.nucleotide_data:
                            #self.seq_main_text_input.text += '\n' + spaces + str(line_idx) + ' ' + i 
                            self.seq_main_text_input.insertPlainText('\n')
                            self.seq_main_text_input.insertPlainText(spaces)
                            self.seq_main_text_input.insertPlainText(str(line_idx))
                            self.seq_main_text_input.insertPlainText(' ')
                            self.seq_main_text_input.insertPlainText(i)
                            line_idx += 1
                            if line_idx > 9: 
                                spaces = '  '
                            
                    elif self.spacer_tog_checkbox.checkState() == 2: 
                        heading_1_of_5_ws = '         {0[0]}          {0[1]}          {0[2]}          {0[3]}          {0[4]}'.format(heading_1_nums)
                        heading_2_of_5_ws = 'Line {0} {0} {0} {0} {0}'.format(heading_2_nums)
                        header = ['\n\n' , self.heading_title , '  [Seq Num {}]'.format(self.seq_selected),
                                    '\n', '     ', heading_1_of_5_ws, '\n', heading_2_of_5_ws ]
                        
                        for i in header: 
                            self.seq_main_text_input.insertPlainText(i)
                            
                        for i in self.nucleotide_data:
                            inserter_lst = ['\n', spaces, str(line), ' ' , ' '.join(self.nucleotide_data[idx][s:s + 10] for s in range(0, len(self.nucleotide_data[idx]), 10))]
                            for i in inserter_lst:
                                self.seq_main_text_input.insertPlainText(i)
                            
                            idx += 1
                            line += 1
                            if line > 9:
                                spaces = '  '
                    else: 
                        self.action_message_text_input.setText('Sequence {} loaded.'.format(self.seq_selected))
                        try: 
                            if (self.seq_selected == False) | (self.seq_selected == ''): 
                                self.action_message_text_input.setText('You need to select a sequence')
                        except: 
                            self.action_message_text_input.setText('You need to load a fasta file first.')
            except: 
                try: 
                    if self.data: 
                        self.action_message_text_input.setText('You need to select a sequence.')
                except: 
                    self.action_message_text_input.setText('You need to load a fasta file first.')
        
        def show_seq_data_by_100(self): 
            ''' 
            Intended to print the raw string of seq selected to kivy output. It displays 
            the DNA sequence by 100. An exception is raised if a .fasta file is not 
            yet loaded or a sequence has not been selected. 
            [Works as intended] 
            ''' 
            try:     
                self.seq_main_text_input.moveCursor(1, 0)
                if (self.seq_selected) and (self.seq_selected in self.seq_keys_nums):
                    file_data_2 = self.data.split('>')[1:] #converts file_data to list of lists
                    #REMOVE Ns HERE 
                    table = str.maketrans('', '', 'N') 
                    file_data_2 = [s.translate(table) for s in file_data_2]
                    
                    seq_selected_idx = int(self.seq_selected) 
                    #format the text...
                    #file_data_2[seq_selected_idx] is string data of the sequence selected 
                    scoped_data = file_data_2[seq_selected_idx].split('\n')[0:] #creates a list of all data split by \n
                    self.heading_title = scoped_data[0]
                    nucleotide_data = ''.join(map(str,scoped_data[1:]))
                    heading_1_nums = [i for i in range(1,11)]
                    heading_2_nums = '1234567890'
    
                    #self.nucleotide_data = '\n   1 '.join(nucleotide_data[i:i + 50] for i in range(0,len(nucleotide_data), 50))
                    self.nucleotide_data = '\n'.join(nucleotide_data[i:i + 100] for i in range(0,len(nucleotide_data), 100))
                    self.nucleotide_data = self.nucleotide_data.split('\n')
                    idx = 0
                    line = 1
                    spaces = '   '
                    #loop to print each string in the list of strings nucleotides 
                    if self.spacer_tog_checkbox.checkState() == 0:
                            
                        heading_1_of_10_ns = '         {0[0]}         {0[1]}         {0[2]}         {0[3]}         {0[4]}         {0[5]}         {0[6]}         {0[7]}         {0[8]}         {0[9]}'.format(heading_1_nums)
                        heading_2_of_10_ns = 'Line {0}{0}{0}{0}{0}{0}{0}{0}{0}{0}'.format(heading_2_nums)
                        header = ['\n\n' , self.heading_title , '  [Seq Num {}]'.format(self.seq_selected),
                                    '\n', '     ', heading_1_of_10_ns, '\n', heading_2_of_10_ns ]
                        
                        for i in header: 
                            self.seq_main_text_input.insertPlainText(i)
                        line_idx = 1
                        spaces = '   '
                        
                        for i in self.nucleotide_data:
                            self.seq_main_text_input.insertPlainText('\n')
                            self.seq_main_text_input.insertPlainText(spaces)
                            self.seq_main_text_input.insertPlainText(str(line_idx))
                            self.seq_main_text_input.insertPlainText(' ')
                            self.seq_main_text_input.insertPlainText(i)
                            line_idx += 1
                            if line_idx > 9: 
                                spaces = '  '
                    elif self.spacer_tog_checkbox.checkState() == 2:
                        heading_1_of_10_ws = '         {0[0]}          {0[1]}          {0[2]}          {0[3]}          {0[4]}          {0[5]}          {0[6]}          {0[7]}          {0[8]}          {0[9]}'.format(heading_1_nums)
                        heading_2_of_10_ws = 'Line {0} {0} {0} {0} {0} {0} {0} {0} {0} {0}'.format(heading_2_nums)
                        inserter_lst = ['\n\n', self.heading_title, '  [Seq Num {}]'.format(self.seq_selected), 
                                        '\n', '     ', heading_1_of_10_ws, '\n', heading_2_of_10_ws]
                        for i in inserter_lst:
                            self.seq_main_text_input.insertPlainText(i)
                        insterter_lst_data = ['\n', spaces, str(line), ' ' , ' '.join(self.nucleotide_data[idx][s:s + 10] for s in range(0, len(self.nucleotide_data[idx]), 10))]
                        for i in self.nucleotide_data:
                            inserter_lst = ['\n', spaces, str(line), ' ' , ' '.join(self.nucleotide_data[idx][s:s + 10] for s in range(0, len(self.nucleotide_data[idx]), 10))]
                            for i in inserter_lst:
                                self.seq_main_text_input.insertPlainText(i)
                            idx += 1
                            line += 1
                            if line > 9:
                                spaces = '  '
            except: 
                try: 
                    if self.data: 
                        self.action_message_text_input.setText('You need to select a sequence.')
                except: 
                    self.action_message_text_input.setText('You need to load a fasta file first.')
                    
        def plot_gc(self): 
            '''
            Function that generates a bar plot of all the GC nucleotide data in the 
            sequence that is selected. 
            '''
            try:     
                if (self.seq_selected) and (self.seq_selected in self.seq_keys_nums):
                    #step 1: split each self.original_data_lists into 10 equal segments
                    org_len_lst = [len(i) for i in self.data_vals]
                    self.org_len_div_10 = [int(i/10) for i in org_len_lst]
                    #access the list within the list of lists self.original_data_lists and apply org_len_lst/10
                    #plot_lists = [i/10 for i in org_len_lst]
                    spliter_idx = 0
                    self.org_data_by_10 = []
                    for items_in_lst in self.data_vals:
                            new_list = ','.join(items_in_lst[s:s + self.org_len_div_10[spliter_idx]] for s in range(0,len(items_in_lst), self.org_len_div_10[spliter_idx]))
                            new_list = new_list.split(',')
                            self.org_data_by_10.append(new_list)  
                            spliter_idx += 1
                    #Count GC Content in each segment of target seq 
                    self.count_gc_per_fin = []
                    temp_idx = 0
                    for i in self.org_data_by_10[int(self.seq_selected)]:
                        count_gc_per = i.count('GC')
                        self.count_gc_per_fin.append(count_gc_per)
                        temp_idx += 1
                        
                    self.gen_idx_lst = []
                    for i in range(len(self.count_gc_per_fin)):
                        self.gen_idx_lst.append(i)
                    self.data_to_df = pd.DataFrame({'seq': self.gen_idx_lst,
                                                    'gc_count': self.count_gc_per_fin})
                    self.data_to_df['proportion'] = self.data_to_df['gc_count'] / self.data_to_df['gc_count'].sum()
                    #Bin each self.original_data_lists into 3 or 5 
                    #xaxis = gc content ; yaxis = count of segments per bin
                    #Plot the data: 
                    fig, ax = plt.subplots(1,1, figsize=(6,6), dpi=100)
                    
                    ax.hist(x=self.data_to_df['proportion'],
                            bins=5,
                            edgecolor="r", 
                            alpha=1)
                    plt.xticks(rotation = 75)
                    x_label = 'GC Content For Sequence {0} : \n {1}'.format(self.seq_selected , self.data_keys[int(self.seq_selected)])
                    ax.set_xlabel('Value of GC content')
                    ax.set_ylabel('Counts of Segments in Each Bin')
                    ax.set_title(x_label)
                    plt.show()
                else: 
                    try: 
                        if self.data: 
                            self.action_message_text_input.setText('You need to select a sequence.')
                    except: 
                        self.action_message_text_input.setText('You need to load a fasta file first.')
            except: 
                try: 
                    if self.data: 
                        self.action_message_text_input.setText('You need to select a sequence.')
                except: 
                    self.action_message_text_input.setText('You need to load a fasta file first.')

        #ASSIGN THE BUTTUN THE FUNCTION EVENT #STEP 10
        #note; I'm not sure yet why this works--but it does. It fixes the bool error commented below
        #I think it may have something to do with how the layouts or widgets access each other in the Qt module
        #AttributeError: 'bool' object has no attribute 'left_layout_textedit'        
        self.load_fasta_button.clicked.connect(lambda checked: load_file(self))
        self.select_seq_button.clicked.connect(lambda checked: select_seq(self))
        self.select_seq_button.clicked.connect(lambda checked: select_seq_ana(self))
        self.show_seq_data_by_50.clicked.connect(lambda checked: show_seq_data_by_50(self))
        self.show_seq_data_by_100.clicked.connect(lambda checked: show_seq_data_by_100(self))
        self.plot_gc_button.clicked.connect(lambda checked: plot_gc(self))
        #ADD THE MAIN WINDOWS OUTER LAYOUT (MASTER LAYOUT) #STEP 2
        self.setLayout(self.outerLayout)
        #SET WINDOW TITLE
        self.setWindowTitle('ANG DNA Parser')
        #SET DEFUALT MINIMUM SIZE OF WINDOW
        self.setMinimumSize(1600,900)
        
if __name__ == "__main__":
    app = QApplication(sys.argv)
    app.setStyle('Fusion') 
    window = WindowClassOne()
    window.show()
    sys.exit(app.exec_())
        



Kivy Version

Kivy Version Demo Video:


Imports


#KIVY IMPORTS
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.core.window import Window
from kivy.uix.floatlayout import FloatLayout
from kivy.properties import ObjectProperty
from kivy.uix.popup import Popup
#DATA GRAPH IMPORTS
import pandas as pd
import matplotlib.pyplot as plt
#GENERAL IMPORTS
from collections import Counter
import os

Imbedding the kivy file

Kivy uses a .kv file that can be either imbedded or external to the python file. The method I use here is embedding the .kv code into the Python code. The .kv file makes it easier to manipulate the layout of the application. It makes instantiating classes, widgets, and assigning events a whole lot easier too. Here is a brief snapshot of how the kv language works. Kivy_lang_snap

If we ran this in it's simplest form it looks like this.


  #KIVY IMPORTS
  from kivy.app import App
  from kivy.lang import Builder
  from kivy.uix.screenmanager import ScreenManager, Screen
  from kivy.core.window import Window
  
  #KIVY FILE BUILD IMBEDDED 
  Builder.load_string("""
  <MainScreen>:
      #These instantiat the text box attributes for kivy
      seq_main_text_input : seq_main_text_input
      action_message_text_input : action_message_text_input
  
      GridLayout:
          cols:2
          TextInput:
              id: seq_main_text_input
              text: 'TEXT INPUT 1'
              readonly: True
              font_size: 15
              multiline: True
              font_name: 'DejaVuSansMono.ttf'
              
          GridLayout:
              rows:6
              cols:1
              size_hint:.4, 1
              TextInput:
                  id: action_message_text_input
                  size_hint:1,.2
                  background_color:'black'
                  foreground_color:'red' 
                  font_size:14
                  readonly:True
                  text:'text in action message'
  """)
  class MainScreen(Screen):
      '''
      All of the methods used for the widgets will be here.
      '''
      
  class TestApp(App):
      def build(self):
          # Create the screen manager
          sm = ScreenManager()
          sm.add_widget(MainScreen(name='menu'))
          Window.size = (1600,900); Window.minimum_width, Window.minimum_height = Window.size
          return sm
  #Factory.register('LoadDialog', cls=LoadDialog)
  if __name__ == '__main__':
      TestApp().run()

When run, we get:
basic_kivy_ex

You can keep adding widgets and layouts to get what you need.


  #KIVY IMPORTS
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.core.window import Window

#KIVY FILE BUILD IMBEDDED 
Builder.load_string("""
<MainScreen>:
    #These instantiat the text box attributes for kivy
    seq_main_text_input : seq_main_text_input
    action_message_text_input : action_message_text_input

    GridLayout:
        cols:2
        TextInput:
            id: seq_main_text_input
            text: 'TEXT INPUT 1'
            readonly: True
            font_size: 15
            multiline: True
            font_name: 'DejaVuSansMono.ttf'
            
        GridLayout:
            rows:6
            cols:1
            size_hint:.4, 1
            TextInput:
                id: action_message_text_input
                size_hint:1,.2
                background_color:'black'
                foreground_color:'red' 
                font_size:14
                readonly:True
                text:'text in action message'
            GridLayout: 
                rows:1
                cols:1
                size_hint:1,1
                TextInput: 
                    id: side_bar_text_input
                    text:'text in stats'
            GridLayout: 
                rows:1
                cols:1
                size_hint: .2,.2
                Button:
                    text:'Load .fasta'
                    on_release: root.show_load()
""")
class MainScreen(Screen):
    '''
    All of the methods used for the widgets will be here.
    '''
    
class TestApp(App):
    def build(self):
        # Create the screen manager
        sm = ScreenManager()
        sm.add_widget(MainScreen(name='menu'))
        Window.size = (1600,900); Window.minimum_width, Window.minimum_height = Window.size
        return sm
#Factory.register('LoadDialog', cls=LoadDialog)
if __name__ == '__main__':
    TestApp().run()

Which yeilds: basic_kivy_ex_2

So you can have all the widgets in the layout crafted into the Kivy window. The catch here is that they won't do anything until you write and assign functions to do xyz.

  
#KIVY IMPORTS
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.core.window import Window

#KIVY FILE BUILD IMBEDDED 
Builder.load_string("""
<MainScreen>:
    #These instantiat the text box attributes for kivy
    seq_main_text_input : seq_main_text_input
    side_bar_text_input : side_bar_text_input
    action_message_text_input : action_message_text_input
    seq_select : seq_select
    spacer_toggle_text:spacer_toggle_text
    
    GridLayout:
        cols:2
        TextInput:
            id: seq_main_text_input
            text: ''
            readonly: True
            font_size: 15
            multiline: True
            font_name: 'DejaVuSansMono.ttf'
            
        GridLayout:
            rows:6
            cols:1
            size_hint:.4, 1
            TextInput:
                id: action_message_text_input
                size_hint:1,.2
                background_color:'black'
                foreground_color:'red' 
                font_size:14
                readonly:True
                text:'text in action message'
            GridLayout: 
                rows:1
                cols:1
                size_hint:1,1
                TextInput: 
                    id: side_bar_text_input
                    text:'text in stats'
            GridLayout: 
                rows:1
                cols:1
                size_hint: .2,.2
                Button:
                    text:'Load .fasta'
                    on_release: root.show_load()
            GridLayout:
                rows:2
                cols:2
                size_hint:1,.25
                Button:
                    text: 'Select Sequence'
                    on_release: root.select_seq()
                TextInput:
                    id: seq_select
                Button:
                    text: 'Spacer Toggle'
                    on_release: root.toggle_spacer()
                TextInput:
                    id: spacer_toggle_text
                    background_color: 'black'
                    foreground_color: 'grey'
                    readonly:True
            GridLayout: 
                rows:1
                cols:2
                size_hint:.2,.2
                Button: 
                    text: 'Show Sequence Data by 50'
                    on_release: root.show_seq_data_by_50()  
                Button:
                    text: 'Show Sequence Data by 100'
                    on_release: root.show_seq_data_by_100() 
            
            ScrollView:
                Button:
                    size_hint:1,.2
                    text: 'Plot GC'
                    on_release: root.plot_gc()
                
#################LOAD POPUP#################
<LoadDialog>: 
    RelativeLayout: 
        size: root.size
        pos: root.pos
        orientation: "vertical"
        FileChooserListView: 
            id: filechooser
        BoxLayout: 
            size_hint_y: None
            height: 30
            Button: 
                text: "Cancel"
                on_release: root.cancel()
            Button: 
                text: "Load"
                on_release: root.load(filechooser.path, filechooser.selection)
""")
class MainScreen(Screen):
    '''
    All of the methods used for the widgets will be here.
    '''
    
class TestApp(App):
    def build(self):
        # Create the screen manager
        sm = ScreenManager()
        sm.add_widget(MainScreen(name='menu'))
        Window.size = (1600,900); Window.minimum_width, Window.minimum_height = Window.size
        return sm
#Factory.register('LoadDialog', cls=LoadDialog)
if __name__ == '__main__':
    TestApp().run()
  

Giving us:

basic_kivy_ex_3

Starting from the above the kivy code is pretty much done. We now have to add to the python code; a LoadDialog class, and write up functions to do things when buttons (widgets) are pressed.


'''
Declare LoadDialog Screen (class). This is mostly built into kivy. We just have to 
call this class in Python almost like a place holder for the kivy code above. 
'''
class LoadDialog(FloatLayout):
    load = ObjectProperty(None)
    cancel = ObjectProperty(None)

class MainScreen(Screen):
    '''
    All of the methods used for the widgets will be here. So, buttons and anything 
    that is displayed in the application. This class will house all the methods. 
    '''
    def show_load(self):
        '''
        Defines and calls the load file popup. 
        '''
        content = LoadDialog(load=self.load, cancel=self.dismiss_popup) 
        self._popup = Popup(title="Load file", content=content,
                            size_hint=(0.9, 0.9))
        self._popup.open()



Full Kivy Version


# -*- coding: utf-8 -*-
"""
@author: ANG
"""
#KIVY IMPORTS
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.core.window import Window
from kivy.uix.floatlayout import FloatLayout
from kivy.properties import ObjectProperty
from kivy.uix.popup import Popup
#DATA GRAPH IMPORTS
import pandas as pd
import matplotlib.pyplot as plt
#GENERAL IMPORTS
from collections import Counter
import os

#KIVY FILE BUILD IMBEDDED 
Builder.load_string("""
<MainScreen>:
    #These instantiat the text box attributes for kivy
    seq_main_text_input : seq_main_text_input
    side_bar_text_input : side_bar_text_input
    action_message_text_input : action_message_text_input
    seq_select : seq_select
    spacer_toggle_text:spacer_toggle_text
    
    GridLayout:
        cols:2
        TextInput:
            id: seq_main_text_input
            text: ''
            readonly: True
            font_size: 15
            multiline: True
            font_name: './DejaVuSansMono.ttf'
            
        GridLayout:
            rows:6
            cols:1
            size_hint:.4, 1
            TextInput:
                id: action_message_text_input
                size_hint:1,.2
                background_color:'black'
                foreground_color:'red' 
                font_size:14
                readonly:True
                text:'text in action message'
            GridLayout: 
                rows:1
                cols:1
                size_hint:1,1
                TextInput: 
                    id: side_bar_text_input
                    text:'text in stats'
            GridLayout: 
                rows:1
                cols:1
                size_hint: .2,.2
                Button:
                    text:'Load .fasta'
                    on_release: root.show_load()
            GridLayout:
                rows:2
                cols:2
                size_hint:1,.25
                Button:
                    text: 'Select Sequence'
                    on_release: root.select_seq()
                TextInput:
                    id: seq_select
                    multiline: False
                    input_filter: 'int'
                Button:
                    text: 'Spacer Toggle'
                    on_release: root.toggle_spacer()
                TextInput:
                    id: spacer_toggle_text
                    background_color: 'black'
                    foreground_color: 'grey'
                    readonly:True
            GridLayout: 
                rows:1
                cols:2
                size_hint:.2,.2
                Button: 
                    text: 'Show Sequence Data by 50'
                    on_release: root.show_seq_data_by_50()  
                Button:
                    text: 'Show Sequence Data by 100'
                    on_release: root.show_seq_data_by_100() 
            
            ScrollView:
                Button:
                    size_hint:1,.2
                    text: 'Plot GC'
                    on_release: root.plot_gc()
                
#################LOAD POPUP#################
<LoadDialog>: 
    RelativeLayout: 
        size: root.size
        pos: root.pos
        orientation: "vertical"
        FileChooserListView: 
            id: filechooser
            path: './'
        BoxLayout: 
            size_hint_y: None
            height: 30
            Button: 
                text: "Cancel"
                on_release: root.cancel()
            Button: 
                text: "Load"
                on_release: root.load(filechooser.path, filechooser.selection)
""")

#Declare LoadDialog Screen (class)
class LoadDialog(FloatLayout):
    load = ObjectProperty(None)
    cancel = ObjectProperty(None)
    
#Declare MainScreen (class)
class MainScreen(Screen):
    '''
    All of the methods used for the widgets will be here.
    '''
    def show_load(self):
        '''
        Defines and calls the load file popup. 
        '''
        content = LoadDialog(load=self.load, cancel=self.dismiss_popup) 
        self._popup = Popup(title="Load file", content=content,
                            size_hint=(0.9, 0.9))
        self._popup.open()

    def load(self, path, filename):
        ''' 
        Intended to load the file as an object but not return it to the kivy output
        [Works as intended] 
        ''' 
        txt = open(os.path.join(path, filename[0]),'r').read() 
        #at this point in load() the data in the file is a raw string
        self.file_data = txt  #creates an obj of the data for class Root
                                #txt is the full text object as a string; 
                                #self.text_input.text
                                #self.text_input.text will output in kivy
                                #txt can be manipulated with any string methods 
                                #to return a desired output but the 
                                #final txt must be a string and only one str 
        filename_str = filename[0].split('\\') 
        #Segment the data into respective [data_keys] [data_vals] lists
        genes = self.file_data.split('>')[1:] 
        seqdic = {} 
        for gene in genes:
            gene = gene.strip().split('\n') 
            seqdic['>'+gene[0]] = ''.join(gene[1:]) 
        seqdic_keys_lst = list(seqdic.keys()) 
        seqdic_vals_lst = list(seqdic.values()) 
        self.original_data_lists = seqdic_vals_lst 
        #Remove a string value from a list of strings
        #seqdic_vals_lst = [s.translate(table) for s in seqdic_vals_lst] 
        self.data_keys = seqdic_keys_lst 
        self.data_vals = seqdic_vals_lst 
        self.spacer = 'Off'
        #SIDE BAR DISPLAY
        self.side_bar_text_1 = 'There are {0} sequences in {1}\n'.format(len(self.data_keys), filename_str[-1]) 
        self.side_bar_text_2 = 'Sequence selection range: 0 - {}\n'.format(len(self.data_keys)-1)
        self.side_bar_text_3 = '\nSequence Selected: '   
        self.side_bar_text_input.text = self.side_bar_text_1 + self.side_bar_text_2 + self.side_bar_text_3 + '\n' 
        self.action_message_text_input.text = 'Fasta file has loaded...' 
        self.dismiss_popup() #Kills the load screen
        
    def dismiss_popup(self):
        self._popup.dismiss()
    
    def select_seq(self):
        ''' 
        Intended to select sequence. The reason for the try/except statements here 
        is that user must do two things before they can select a sequence. The 
        user must load a .fasta file and input a seq number into the select sequence 
        text input. If one of these actions is not taken an exception is raised and 
        the app will display an error in the action_message_text_input.text. 
        Otherwise, if both actions are satified, the app will parse the selected 
        sequence and display information for the selected seq in the sidebar. 
        '''
        self.seq_selected = self.seq_select.text 
        try: 
            self.seq_keys_nums = [str(i) for i in range(0, len(self.data_keys))] 
            if self.seq_selected not in self.seq_keys_nums:
                self.action_message_text_input.text = 'The sequence you selected is out of range.\nOr you need to input an integer.'
            else: 
                file_data_2 = self.file_data.split('>')[1:] #converts file_data to list of lists
                seq_selected_idx = int(self.seq_selected) 
                #format the text...
                #file_data_2[seq_selected_idx] is string data of the sequence selected 
                scoped_data = file_data_2[seq_selected_idx].split('\n')[0:] #creates a list of all data split by \n
                self.heading_title = scoped_data[0]
                self.side_bar_text_3 = '\nSequence Selected: {}'.format(self.seq_selected)
                self.action_message_text_input.text = 'Sequence {} selected...'.format(self.seq_selected)
                #STATS SIDE BAR PARSE
                counter_1 = Counter()
                for i in self.original_data_lists[int(self.seq_selected)]:
                    counter_1[i] += 1
                counter_1 = dict(counter_1)
                self.counter_at = self.original_data_lists[int(self.seq_selected)].count('AT')
                self.counter_gc = self.original_data_lists[int(self.seq_selected)].count('GC')
                self.counter_cn = self.original_data_lists[int(self.seq_selected)].count('N')
                self.n_counts_header = '\nNucleotide counts: \n'
                n_counts_lst = []
                for k, v in counter_1.items(): 
                    n_counts = '{} = {}'.format(k, str(v))
                    n_counts_lst.append(n_counts)
                self.n_counts_lst_str = '\n'.join(n_counts_lst)
                self.total = self.counter_at + self.counter_gc 
                self.at_stat = 'AT = {}  :  AT content = {}'.format(str(self.counter_at), str(self.counter_at/self.total))
                self.gc_stat = 'GC = {}  :  GC content = {}'.format(str(self.counter_gc), str(self.counter_gc/self.total))
                self.stats_full = [[i + '\n' for i in n_counts_lst], '\n', self.at_stat, self.gc_stat]
                self.side_bar_text_input.text = self.side_bar_text_1 + self.side_bar_text_2 + self.side_bar_text_3 + '\n' + self.n_counts_header + self.n_counts_lst_str + '\n' + self.at_stat + '\n' + self.gc_stat 
        except: 
            try:
                if self.file_data:
                    self.action_message_text_input.text = 'You need to input an integer.'
                else: 
                    self.action_message_text_input.text = 'You need to load a fasta file first.'
            except:
                self.action_message_text_input.text = 'You need to load a fasta file first.'

    def toggle_spacer(self): 
        '''
        This function turns the space on/off every ten nucleotides. It will return 
        an error to the action_message_text_input if the user has not loaded a 
        .fasta file or selected a seq. 
        '''
        try: 
            if self.spacer == 'On':     
                self.spacer = 'Off'
                self.spacer_toggle_text.text = self.spacer
            elif self.spacer == 'Off':
                self.spacer = 'On'
                self.spacer_toggle_text.text = self.spacer
        except: 
            try: 
                if self.file_data: 
                    self.action_message_text_input.text = 'You need to select a sequence.'
            except: 
                self.action_message_text_input.text = 'You need to load a fasta file first.'
    
    def show_seq_data_by_50(self): 
        ''' 
        Function intended to print the raw string of seq selected to kivy output. It displays 
        the DNA sequence by 50. An exception is raised if a .fasta file is not 
        yet loaded or a sequence has not been selected. 
        [Works as intended] 
        ''' 
        #at this point self.file_data is the input file as a raw string
        try:     
            if (self.seq_selected) and (self.seq_selected in self.seq_keys_nums):
                file_data_2 = self.file_data.split('>')[1:] #converts file_data to list of lists
                #REMOVE Ns HERE 
                table = str.maketrans('', '', 'N') 
                file_data_2 = [s.translate(table) for s in file_data_2]
                seq_selected_idx = int(self.seq_selected) 
                #format the text...
                #file_data_2[seq_selected_idx] is string data of the sequence selected 
                scoped_data = file_data_2[seq_selected_idx].split('\n')[0:] #creates a list of all data split by \n
                self.heading_title = scoped_data[0]
                nucleotide_data = ''.join(map(str,scoped_data[1:]))
                heading_1_nums = [i for i in range(1,11)]
                heading_2_nums = '1234567890'

                #self.nucleotide_data = '\n   1 '.join(nucleotide_data[i:i + 50] for i in range(0,len(nucleotide_data), 50))
                self.nucleotide_data = '\n'.join(nucleotide_data[i:i + 50] for i in range(0,len(nucleotide_data), 50))
                self.nucleotide_data = self.nucleotide_data.split('\n')
                idx = 0
                line = 1
                spaces = '   '
                #print each string in the list of strings nucleotides 
                if self.spacer == 'Off':
                    
                    heading_1_of_5_ns = '         {0[0]}         {0[1]}         {0[2]}         {0[3]}         {0[4]}'.format(heading_1_nums)
                    heading_2_of_5_ns = 'Line {0}{0}{0}{0}{0}'.format(heading_2_nums)
                    
                    self.seq_main_text_input.text += '\n\n' + self.heading_title + '  [Seq Num {}]'.format(self.seq_selected)
                    self.seq_main_text_input.text += '\n' + '     ' + heading_1_of_5_ns
                    self.seq_main_text_input.text += '\n' + heading_2_of_5_ns

                    line_idx = 1
                    spaces = '   '
                    for i in self.nucleotide_data:
                        self.seq_main_text_input.text += '\n' + spaces + str(line_idx) + ' ' + i 
                        line_idx += 1
                        if line_idx > 9: 
                            spaces = '  '
                            
                elif self.spacer == 'On': 
                    heading_1_of_5_ws = '         {0[0]}          {0[1]}          {0[2]}          {0[3]}          {0[4]}'.format(heading_1_nums)
                    heading_2_of_5_ws = 'Line {0} {0} {0} {0} {0}'.format(heading_2_nums)
                    self.seq_main_text_input.text += '\n\n' + self.heading_title + '  [Seq Num {}]'.format(self.seq_selected)
                    self.seq_main_text_input.text += '\n' + '     ' + heading_1_of_5_ws
                    self.seq_main_text_input.text += '\n' + heading_2_of_5_ws
                    for i in self.nucleotide_data:
                        self.seq_main_text_input.text += '\n' + spaces + str(line) + ' '  + ' '.join(self.nucleotide_data[idx][s:s + 10] for s in range(0, len(self.nucleotide_data[idx]), 10))
                        idx += 1
                        line += 1
                        if line > 9:
                            spaces = '  '
                    
                self.action_message_text_input.text = 'Sequence {} loaded.'.format(self.seq_selected)

            else: 
                try: 
                    if self.file_data: 
                        self.action_message_text_input.text = 'You need to select a sequence.'
                except: 
                    self.action_message_text_input.text = 'You need to load a fasta file first.'
        except: 
            try: 
                if self.file_data: 
                    self.action_message_text_input.text = 'You need to select a sequence.'
            except: 
                self.action_message_text_input.text = 'You need to load a fasta file first.'

    def show_seq_data_by_100(self): 
        ''' 
        Intended to print the raw string of seq selected to kivy output. It displays 
        the DNA sequence by 100. An exception is raised if a .fasta file is not 
        yet loaded or a sequence has not been selected. 
        [Works as intended] 
        ''' 
        try:     
            if (self.seq_selected) and (self.seq_selected in self.seq_keys_nums):
                file_data_2 = self.file_data.split('>')[1:] #converts file_data to list of lists
                #REMOVE Ns HERE 
                table = str.maketrans('', '', 'N') 
                file_data_2 = [s.translate(table) for s in file_data_2]
                seq_selected_idx = int(self.seq_selected) 
                #format the text...
                #file_data_2[seq_selected_idx] is string data of the sequence selected 
                scoped_data = file_data_2[seq_selected_idx].split('\n')[0:] #creates a list of all data split by \n
                self.heading_title = scoped_data[0]
                nucleotide_data = ''.join(map(str,scoped_data[1:]))
                heading_1_nums = [i for i in range(1,11)]
                heading_2_nums = '1234567890'

                #self.nucleotide_data = '\n   1 '.join(nucleotide_data[i:i + 50] for i in range(0,len(nucleotide_data), 50))
                self.nucleotide_data = '\n'.join(nucleotide_data[i:i + 100] for i in range(0,len(nucleotide_data), 100))
                self.nucleotide_data = self.nucleotide_data.split('\n')
                idx = 0
                line = 1
                spaces = '   '
                #loop to print each string in the list of strings nucleotides 
                if self.spacer == 'Off':
                        
                    heading_1_of_10_ns = '         {0[0]}         {0[1]}         {0[2]}         {0[3]}         {0[4]}         {0[5]}         {0[6]}         {0[7]}         {0[8]}         {0[9]}'.format(heading_1_nums)
                    heading_2_of_10_ns = 'Line {0}{0}{0}{0}{0}{0}{0}{0}{0}{0}'.format(heading_2_nums)
                        
                    self.seq_main_text_input.text += '\n\n' + self.heading_title + '  [Seq Num {}]'.format(self.seq_selected)
                    self.seq_main_text_input.text += '\n' + '     ' + heading_1_of_10_ns
                    self.seq_main_text_input.text += '\n' + heading_2_of_10_ns

                    line_idx = 1
                    spaces = '   '
                    for i in self.nucleotide_data:
                        self.seq_main_text_input.text += '\n' + spaces + str(line_idx) + ' ' + i 
                        line_idx += 1
                        if line_idx > 9: 
                            spaces = '  '
                            
                elif self.spacer == 'On': 
                    heading_1_of_10_ws = '         {0[0]}          {0[1]}          {0[2]}          {0[3]}          {0[4]}          {0[5]}          {0[6]}          {0[7]}          {0[8]}          {0[9]}'.format(heading_1_nums)
                    heading_2_of_10_ws = 'Line {0} {0} {0} {0} {0} {0} {0} {0} {0} {0}'.format(heading_2_nums)
                    self.seq_main_text_input.text += '\n\n' + self.heading_title + '  [Seq Num {}]'.format(self.seq_selected)
                    self.seq_main_text_input.text += '\n' + '     ' + heading_1_of_10_ws
                    self.seq_main_text_input.text += '\n' + heading_2_of_10_ws
                    for i in self.nucleotide_data:
                        self.seq_main_text_input.text += '\n' + spaces + str(line) + ' '  + ' '.join(self.nucleotide_data[idx][s:s + 10] for s in range(0, len(self.nucleotide_data[idx]), 10))
                        idx += 1
                        line += 1
                        if line > 9:
                            spaces = '  '
                    
                self.action_message_text_input.text = 'Sequence {} loaded.'.format(self.seq_selected)
                
            else: 
                try: 
                    if self.file_data: 
                        self.action_message_text_input.text = 'You need to select a sequence.'
                except: 
                    self.action_message_text_input.text = 'You need to load a fasta file first.'
        except: 
            try: 
                if self.file_data: 
                    self.action_message_text_input.text = 'You need to select a sequence.'
            except: 
                self.action_message_text_input.text = 'You need to load a fasta file first.'
                
    def plot_gc(self): 
        '''
        Function that generates a bar plot of all the GC nucleotide data in the 
        sequence that is selected. 
        '''
        try:     
            if (self.seq_selected) and (self.seq_selected in self.seq_keys_nums):
                #step 1: split each self.original_data_lists into 10 equal segments
                org_len_lst = [len(i) for i in self.data_vals]
                self.org_len_div_10 = [int(i/10) for i in org_len_lst]
                #access the list within the list of lists self.original_data_lists and apply org_len_lst/10
                spliter_idx = 0
                self.org_data_by_10 = []
                for items_in_lst in self.data_vals:
                        new_list = ','.join(items_in_lst[s:s + self.org_len_div_10[spliter_idx]] for s in range(0,len(items_in_lst), self.org_len_div_10[spliter_idx]))
                        new_list = new_list.split(',')
                        self.org_data_by_10.append(new_list)  
                        spliter_idx += 1
                #Count GC Content in each segment of target seq 
                self.count_gc_per_fin = []
                temp_idx = 0
                for i in self.org_data_by_10[int(self.seq_selected)]:
                    count_gc_per = i.count('GC')
                    self.count_gc_per_fin.append(count_gc_per)
                    temp_idx += 1
                    
                self.gen_idx_lst = []
                for i in range(len(self.count_gc_per_fin)):
                    self.gen_idx_lst.append(i)
                self.data_to_df = pd.DataFrame({'seq': self.gen_idx_lst,
                                                'gc_count': self.count_gc_per_fin})
                self.data_to_df['proportion'] = self.data_to_df['gc_count'] / self.data_to_df['gc_count'].sum()
                #Bin each self.original_data_lists into 3 or 5 
                #xaxis = gc content ; yaxis = count of segments per bin
                #Plot the data: 
                fig, ax = plt.subplots(1,1, figsize=(10,10))
                ax.hist(x=self.data_to_df['proportion'],
                        bins=5,
                        edgecolor="r", 
                        alpha=1)
                plt.xticks(rotation = 75)
                x_label = 'GC Content For Sequence {0} : \n {1}'.format(self.seq_selected , self.data_keys[int(self.seq_selected)])
                ax.set_xlabel('Value of GC content')
                ax.set_ylabel('Counts of Segments in Each Bin')
                ax.set_title(x_label)
                plt.show()
            else: 
                try: 
                    if self.file_data: 
                        self.action_message_text_input.text = 'You need to select a sequence.'
                except: 
                    self.action_message_text_input.text = 'You need to load a fasta file first.'
        except: 
            try: 
                if self.file_data: 
                    self.action_message_text_input.text = 'You need to select a sequence.'
            except: 
                self.action_message_text_input.text = 'You need to load a fasta file first.'
class ANG_DNA_SEQApp(App):
    def build(self):
        #Create the screen manager
        sm = ScreenManager()
        sm.add_widget(MainScreen(name='menu'))
        Window.size = (1600,900); Window.minimum_width, Window.minimum_height = Window.size
        return sm
if __name__ == '__main__':
    ANG_DNA_SEQApp().run()